module ActiveRecord
  module Acts #:nodoc:
    module KeyedTaggable #:nodoc:
      def self.included(base)
        base.extend(ClassMethods)  
      end
      
      module ClassMethods
        def acts_as_keyed_taggable(options = {})
          write_inheritable_attribute(:acts_as_keyed_taggable_options, {
            :taggable_type => ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s,
            :from => options[:from]
          })
          
          class_inheritable_reader :acts_as_keyed_taggable_options

          has_many :keyed_taggings, :as => :taggable, :dependent => true
          has_many :keyed_tags, :through => :keyed_taggings

          include ActiveRecord::Acts::KeyedTaggable::InstanceMethods
          extend ActiveRecord::Acts::KeyedTaggable::SingletonMethods          
        end
      end
      
      module SingletonMethods
        def find_tagged_with(key, list)
          find_by_sql([
            "SELECT #{table_name}.* FROM #{table_name}, keyed_tags, taggings " +
            "WHERE #{table_name}.#{primary_key} = taggings.taggable_id " +
            "AND taggings.taggable_type = ? " +
            "AND keyed_tags.key = ? " +
            "AND taggings.keyed_tag_id = keyed_tags.id AND keyed_tags.name IN (?)",
            acts_as_taggable_options[:taggable_type], key, list
          ])
        end
      end
      
      module InstanceMethods
        def tag_with_key(key, list)
          KeyedTag.transaction do
            keyed_taggings.destroy(:all)

            already_tagged = []
            KeyedTag.parse(list).each do |name|
              unless already_tagged.include?(name)
                if acts_as_keyed_taggable_options[:from]
                  send(acts_as_taggable_options[:from]).keyed_tags.find_or_create_by_name(name).on(self)
                else
                  KeyedTag.find_or_create_by_name_and_key(name, key).on(self)
                end
              end
              already_tagged << name
            end
          end
        end

        def keyed_tag_list key
          keyed_tags.find(:all, :conditions => ["keyed_tags.key = ?", key]).collect { |tag| tag.name.include?(",") ? "'#{tag.name}'" : tag.name }.join(", ")
        end
      end
    end
  end
end
